/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements. See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership. The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License. You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied. See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

#ifndef _TAOTICS_TLOGGING_H_
#define _TAOTICS_TLOGGING_H_

/**
 * Contains utility macros for debugging and logging.
 *
 */

#ifndef __CPIS_DISABLE_SPDLOG__
#define __CPIS_DISABLE_SPDLOG__
#endif

#ifndef CHAR_BUFFER_SIZE
	#define CHAR_BUFFER_SIZE 16384
#endif

#include <time.h>
#include <sys/types.h>

#if defined(__OHOS__)
	#include <hilog/log.h>
#elif defined(ANDROID) || defined(__ANDROID__)
	#include <thread>
	#include <android/log.h>
#elif !defined(__CPIS_DISABLE_SPDLOG__)
	#include <spdlog/spdlog.h>
	#include <spdlog/cfg/env.h>  // support for loading levels from the environment variable
	#include <spdlog/sinks/basic_file_sink.h>
#else
	#include <thread>
#endif

#include <stdio.h>
#include <stdarg.h>
#include <assert.h>
#include <string>

#if defined(_WIN32)
	#include <Windows.h>
	#include <io.h>
	#include <process.h>
#else
	#include <sys/time.h>
	#include <unistd.h>
#endif

#include <chrono>

#ifdef __WIN32__
	#define GETPID _getpid
#else
	#define GETPID getpid
#endif

#define GETTID std::this_thread::get_id

/**
 * TAOTICS_GLOBAL_DEBUGGING_LEVEL = 0: all debugging turned off, debug macros undefined
 * TAOTICS_GLOBAL_DEBUGGING_LEVEL = 1: all debugging turned on
 */
#ifndef TAOTICS_GLOBAL_DEBUGGING_LEVEL
#define TAOTICS_GLOBAL_DEBUGGING_LEVEL 1
#endif

/**
 * TAOTICS_GLOBAL_LOGGING_LEVEL = 0: all logging turned off, logging macros undefined
 * TAOTICS_GLOBAL_LOGGING_LEVEL = 1: all logging turned on
 */
#ifndef TAOTICS_GLOBAL_LOGGING_LEVEL
#define TAOTICS_GLOBAL_LOGGING_LEVEL 1
#endif

static char szBuffer[CHAR_BUFFER_SIZE] = {0};
#if defined(ANDROID) || defined(__ANDROID__) || defined(__OHOS__)
    static time_t now;
    static char dbgtime[26];
#elif defined(__CPIS_DISABLE_SPDLOG__)
	static bool fprintf_initial = false;
	static FILE *fprintf_logger = stderr;
#else
    static bool spdlog_initial = false;
#endif

void inline _trace(const char* szFormat, ...)
{
    va_list args;
    va_start(args, szFormat);
    size_t nBuf = vsprintf(szBuffer, szFormat, args);
    va_end(args);
    assert(nBuf < sizeof(szBuffer));
#if defined(ANDROID) || defined(__ANDROID__)
     time(&now);
     ctime_r(&now, dbgtime);
	 __android_log_print(ANDROID_LOG_DEBUG, "Taotics", "[%s] [debug] %s\n", dbgtime, szBuffer);
#elif defined(__OHOS__)
    time(&now);
    ctime_r(&now, dbgtime);
    OH_LOG_Print(LOG_APP, LOG_DEBUG, LOG_DOMAIN, "Taotics", "[%{public}s] [debug] %{public}s\n", dbgtime, szBuffer);
#elif defined(__CPIS_DISABLE_SPDLOG__)
    if(!fprintf_initial)
    {
        fprintf_initial = true;

		char *_env = getenv("TAOTICS_GLOBAL_LOG_FILENAME");
		if (_env != nullptr)
		{
			// 创建一个文件日志器，将日志输出到"log.txt"文件，设置这个文件日志器为默认日志器
			fprintf_logger = fopen(_env, "a");
			if (fprintf_logger == nullptr)
			{
				fprintf_logger = stderr;
			}
		}
    }
	fprintf(fprintf_logger, "Taotics : %s\n", szBuffer);
	fflush(fprintf_logger);
#else
    if(!spdlog_initial)
    {
        spdlog_initial = true;

		char *_env = getenv("TAOTICS_GLOBAL_LOG_FILENAME");
		if (_env != nullptr)
		{
			// 创建一个文件日志器，将日志输出到"log.txt"文件，设置这个文件日志器为默认日志器
			spdlog::set_default_logger(spdlog::basic_logger_mt("file_logger", _env));
		}

        //defautl log level
        spdlog::set_level(spdlog::level::debug); // 只显示debug及比debug高级的信息，trace不显示
        spdlog::flush_on(spdlog::level::debug); //当运行时产生的日志级别大于等于debug时，立即将缓存的日志写文件/控制台

        // Set the log level to "info":
        // SPDLOG_LEVEL=info && ./example
        spdlog::cfg::load_env_levels();
    
    }
    spdlog::log(spdlog::level::debug, "Taotics: {}", szBuffer);
#endif
}

static bool _environ_checked = false;
static bool _file_checked = false;

static bool _g_debugging_enabled = false;
static bool _g_logging_enabled = false;

void inline _check_environ()
{
	if (!_environ_checked)
	{
		_environ_checked = true;

		{
			char *_env = getenv("TAOTICS_GLOBAL_DEBUGGING_ENABLED");
			if (_env != nullptr && *_env != '\0')
			{
				switch(*_env)
				{
					case 't':
					case 'T':
					case '1':
						_g_debugging_enabled = true;
						break;

					case 'o':
					case 'O':
						if ((_env[1] == 'n') || (_env[1] == 'N')) _g_debugging_enabled = true;
						break;
				}
			}
		}

		{
			char *_env = getenv("TAOTICS_GLOBAL_LOGGING_ENABLED");
			if (_env != nullptr && *_env != '\0')
			{
				switch(*_env)
				{
					case 't':
					case 'T':
					case '1':
						_g_logging_enabled = true;
						break;

					case 'o':
					case 'O':
						if ((_env[1] == 'n') || (_env[1] == 'N')) _g_logging_enabled = true;
						break;
				}
			}
		}
	}
}

void inline _check_file()
{
#if defined(ANDROID) || defined(_ANDROID_)
	return;
#endif

	if (!_file_checked)
	{
		_file_checked = true;

#ifdef __WIN32__
		const char *home_directory = getenv("PUBLIC");
#else
		const char *home_directory = getenv("HOME");
#endif

		std::string _debugging_enabled_filename;
		std::string _logging_enabled_filename;

		_debugging_enabled_filename.assign(home_directory);
		_debugging_enabled_filename.append("/.config/cpis/debugging.enable");

		_logging_enabled_filename.assign(home_directory);
		_logging_enabled_filename.append("/.config/cpis/logging.enable");

#ifdef __WIN32__
		if (_access(_debugging_enabled_filename.c_str(), 0) == 0)
#else
		if (access(_debugging_enabled_filename.c_str(), F_OK) == 0)
#endif
		{
			_g_debugging_enabled = true;
		}

#ifdef __WIN32__
		if (_access(_logging_enabled_filename.c_str(), 0) == 0)
#else
		if (access(_logging_enabled_filename.c_str(), F_OK) == 0)
#endif
		{
			_g_logging_enabled = true;
		}
	}
}

bool inline _debugging_enabled()
{
	_check_environ();
	_check_file();
	return _g_debugging_enabled;
}

#define TAOTICS_DEBUGGING_ENABLED _debugging_enabled()

bool inline _logging_enabled()
{
	_check_environ();
	_check_file();
	return _g_logging_enabled;
}

#define TAOTICS_LOGGING_ENABLED _logging_enabled()

/**
 * Standard wrapper around fprintf what will prefix the file name and line
 * number to the line. Uses TAOTICS_GLOBAL_DEBUGGING_LEVEL to control whether it is
 * turned on or off.
 *
 * @param format_string
 */
#define TAOTICS_DEBUG(format_string, ...)                                               \
	if (TAOTICS_GLOBAL_DEBUGGING_LEVEL || _debugging_enabled())                         \
	{                                                                                   \
	    _trace("[%s,%d] " format_string " ", __FILE__, __LINE__, ##__VA_ARGS__);        \
	}

/**
 * analogous to TAOTICS_DEBUG but also prints the time
 *
 * @param string  format_string input: printf style format string
 */
#define TAOTICS_DEBUG_T(format_string, ...)                    \
  if (TAOTICS_GLOBAL_DEBUGGING_LEVEL || _debugging_enabled())  \
  {                                                            \
      _trace("[%s,%d@%lu|%lu] " format_string " ",             \
              __FILE__,                                        \
              __LINE__,                                        \
              GETPID(),                                        \
              GETTID(),                                        \
              ##__VA_ARGS__);                                  \
                                                               \
  }

#define TAOTICS_DEBUG_TIMER_BEGIN(x) std::chrono::high_resolution_clock::time_point _cpis_timer_start_##x = std::chrono::high_resolution_clock::now(); TAOTICS_DEBUG_T("[TIMER] [%s] begin", #x);
#define TAOTICS_DEBUG_TIMER_END(x) TAOTICS_DEBUG_T("[TIMER] [%s] finished coast: [%d]us", #x, std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::high_resolution_clock::now() - _cpis_timer_start_##x).count()); \

/**
 * analogous to TAOTICS_DEBUG but uses input level to determine whether or not the string
 * should be logged.
 *
 * @param int     level: specified debug level
 * @param string  format_string input: format string
 */
#define TAOTICS_DEBUG_L(level, format_string, ...)                                       \
  if ((level) > 0)                                                                       \
  {                                                                                      \
    _trace("[%s,%d@%d] " format_string " ", __FILE__, __LINE__,GETPID(), ##__VA_ARGS__); \
  }
/**
 * Explicit error logging. Prints time, file name and line number
 *
 * @param string  format_string input: printf style format string
 */
#define TAOTICS_ERROR(format_string, ...)               \
  {                                                     \
    _trace("[%s,%d@%d] ERROR: " format_string " ",      \
            __FILE__,                                   \
            __LINE__,                                   \
            GETPID(),                                   \
            ##__VA_ARGS__);                             \
  }

/**
 * Analogous to TAOTICS_ERROR, additionally aborting the process.
 * WARNING: macro calls abort(), ending program execution
 *
 * @param string  format_string input: printf style format string
 */
#define TAOTICS_ERROR_ABORT(format_string, ...)                        \
  {                                                                    \
    _trace("[%s,%d@%d] ERROR: Going to abort " format_string " ",      \
            __FILE__,                                                  \
            __LINE__,                                                  \
            GETPID(),                                                  \
            ##__VA_ARGS__);                                            \
    exit(1);                                                           \
  }

/**
 * Log input message
 *
 * @param string  format_string input: printf style format string
 */
#define TAOTICS_LOG_OPER(format_string, ...)                                \
  if (TAOTICS_GLOBAL_LOGGING_LEVEL || _logging_enabled())                   \
  {                                                                         \
      _trace(format_string " ", ##__VA_ARGS__);                             \
  }

#endif // #ifndef _TAOTICS_TLOGGING_H_
